package vip.geeker.iotms.help;


import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;
import vip.geeker.iotms.utils.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.util.List;

@Aspect
@Component
@Slf4j
public class MethodCostAspect {


    @Pointcut("execution(public * vip.geeker.iotms.controller..*.*(..))")
    public void managerMethods() {

    }

    @Around("managerMethods()")
    public Object printMethodCosts(ProceedingJoinPoint joinPoint) {
        String traceLogId = getTraceLogId();
        String uri = getRequestUri(joinPoint);
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sqa = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = sqa.getRequest();
        log.info("【traceLogId:{}】request uri:{}", traceLogId, uri);
        Long beginTime = System.currentTimeMillis();
        String methodChName = "";
        methodChName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
        boolean isAjax = isAjax(joinPoint);
        Object res = null;
        try {
            log.info("【traceLogId:{}】-正在执行:{},请求参数:{}", traceLogId, methodChName, joinPoint.getArgs());
            Object[] objs = joinPoint.getArgs();
            Object obj = joinPoint.proceed(objs);
            if (isAjax) {
                Response resp;
                if (!(obj instanceof Response)) {
                    resp = new Response(obj);
                } else {
                    resp = (Response) obj;
                }
                handleLog(traceLogId, uri, resp);
                log.info("【traceLogId:{}】-执行了:{},请求地址uri:{},请求耗时：{}", traceLogId, methodChName, uri, System.currentTimeMillis() - beginTime);
                return resp;
            } else {
                log.info("【traceLogId:{}】-执行了:{},请求地址uri:{},请求耗时：{}", traceLogId, methodChName, uri, System.currentTimeMillis() - beginTime);
                return obj;
            }
        } catch (Exception e) {
            res = handleException(e, beginTime, isAjax, request, traceLogId);
        } catch (Throwable throwable) {
            res = handleException(new RuntimeException("系统异常！"), beginTime, isAjax, request, traceLogId);
            log.error("【traceLogId:{}】request failed! uri:{},error-msg:{}", traceLogId, uri, throwable.toString());
        }
        return res;
    }

    private boolean isAjax(ProceedingJoinPoint proceedingJoinPoint) {
        Annotation methodAnnotation = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(ResponseBody.class);
        Annotation classAnnotation = proceedingJoinPoint.getTarget().getClass().getAnnotation(RestController.class);
        return ObjectUtils.isNotNullAndEmpty(methodAnnotation) || ObjectUtils.isNotNullAndEmpty(classAnnotation);
    }

    /**
     * 日志处理：如果返回的是分页对象，则缩减打印的日志内容。
     *
     * @param traceLogId 日志跟踪ID
     * @param uri        请求路径
     * @param response   返回结果对象
     * @return
     */
    private Response handleLog(String traceLogId, String uri, Response response) {
        if (response.isSuccess()) {
            Object resObj = response.getData();
            if (ObjectUtils.isNotNullAndEmpty(resObj)) {
                String resObjClassName = resObj.getClass().getName();
                if (resObj instanceof List) {
                    List resList = (List) resObj;
                    log.info("【traceLogId:{}】request uri {} successfully! return type:{},return size:{}", traceLogId, uri,
                            resObjClassName, ObjectUtils.isNullOrEmpty(resObj) ? "0" : ((List) resObj).size());
                    if (ObjectUtils.isNotNullAndEmpty(resList) && resList.size() < 11) {
                        log.info("【traceLogId:{}】return value:{}", traceLogId, resList);
                    }
                } else if (resObj instanceof IPage) {
                    List resList = ((IPage) resObj).getRecords();
                    log.info("【traceLogId:{}】request uri {} successfully! return type:{},return size:{}", traceLogId, uri,
                            resObjClassName, ObjectUtils.isNullOrEmpty(resList) ? "0" : (resList).size());
                    if (ObjectUtils.isNotNullAndEmpty(resList) && resList.size() < 11) {
                        log.info("【traceLogId:{}】return value:{}", traceLogId, resList);
                    }
                } else {
                    log.info("【traceLogId:{}】request uri {} successfully! return type:{},return value:{}", traceLogId, uri, resObjClassName, resObj.toString());
                }
            }
        } else {
            log.warn("【traceLogId:{}】request failed ! uri {},return value:{}", traceLogId, uri, response);
        }
        return response;
    }

    /**
     * 异常处理：
     * 判断是否为ajax请求，如果是则返回Response对象；如果返回的是页面，则统一重定向到错误页面并绑定错误信息。
     *
     * @param e          异常
     * @param startTime  开始时间
     * @param isAjax     是否是AJAX请求
     * @param request    用于绑定错误信息
     * @param traceLogId 请求ID
     * @return Response/ModelAndView
     */
    private Object handleException(Exception e, Long startTime, boolean isAjax, HttpServletRequest request, String traceLogId) {
        if (isAjax) {
            Response response = null;
            if (e instanceof BusinessException) {
                response = new Response();
                BusinessException be = (BusinessException) e;
                response.setCode(be.getCode());
                response.setMessage(be.getMessage());
            } else {
                response = Response.error("系统异常，请稍后再试！");
            }
            log.error("本次请求发生异常，请求ID：{},请求耗时：{},异常信息：{}", traceLogId, System.currentTimeMillis() - startTime, e);
            return response;
        } else {
            ModelAndView mv = new ModelAndView(Constants.ERROR);
            request.setAttribute("message", e.getMessage());
            log.error("本次请求发生异常，请求ID：{},请求耗时：{},异常信息：{}", traceLogId, System.currentTimeMillis() - startTime, e);
            return mv;
        }
    }

    /**
     * 获取请求路径：ClassName+MethodName,非RequestMappingUrl
     *
     * @param joinPoint 切点
     * @return 请求路径(ClassName + MethodName)
     */
    private String getRequestUri(ProceedingJoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getName();
        className = className.substring(className.lastIndexOf(".") + 1);
        String methodName = joinPoint.getSignature().getName();
        return className + "." + methodName;
    }

    /**
     * 生成日志跟踪ID
     *
     * @return traceLogId
     */
    private String getTraceLogId() {
        String mills = String.valueOf(System.currentTimeMillis());
        String tail = String.valueOf((int) Math.round(Math.random() * (9999 - 1000) + 1000));
        return mills + tail;
    }
}
